/*=============================================================================

  Library: CFS

  Copyright (c) German Cancer Research Center,
    Division of Medical and Biological Informatics

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

=============================================================================*/

#include "CFSServices_p.h"

#include <QStringListIterator>
#include <QMutexLocker>
#include <QBuffer>

#include <algorithm>

#include "CFSServiceFactory.h"
#include "CFSPluginConstants.h"
#include "CFSPluginFrameworkContext_p.h"
#include "CFSServiceException.h"
#include "CFSServiceRegistration_p.h"
#include "CFSLDAPExpr_p.h"

//----------------------------------------------------------------------------
struct ServiceRegistrationComparator
{
  bool operator()(const CFSServiceRegistration& a, const CFSServiceRegistration& b) const
  {
    return a < b;
  }
};

//----------------------------------------------------------------------------
CFSDictionary CFSServices::createServiceProperties(const CFSDictionary& in,
                                                       const QStringList& classes,
                                                       long sid)
{
  static qlonglong nextServiceID = 1;
  CFSDictionary props = in;

  if (!classes.isEmpty())
  {
    props.insert(CFSPluginConstants::OBJECTCLASS, classes);
  }

  props.insert(CFSPluginConstants::SERVICE_ID, sid != -1 ? sid : nextServiceID++);

  return props;
}

//----------------------------------------------------------------------------
CFSServices::CFSServices(CFSPluginFrameworkContext* fwCtx)
  : mutex(), framework(fwCtx)
{

}

//----------------------------------------------------------------------------
CFSServices::~CFSServices()
{
  clear();
}

//----------------------------------------------------------------------------
void CFSServices::clear()
{
  services.clear();
  classServices.clear();
  framework = 0;
}

//----------------------------------------------------------------------------
CFSServiceRegistration CFSServices::registerService(CFSPluginPrivate* plugin,
                             const QStringList& classes,
                             QObject* service,
                             const CFSDictionary& properties)
{
  if (service == 0)
  {
    throw CFSInvalidArgumentException("Can't register 0 as a service");
  }

  // Check if service implements claimed classes and that they exist.
  for (QStringListIterator i(classes); i.hasNext();)
  {
    QString cls = i.next();
    if (cls.isEmpty())
    {
      throw CFSInvalidArgumentException("Can't register as null class");
    }

    if (!(qobject_cast<CFSServiceFactory*>(service)))
    {
      if (!checkServiceClass(service, cls))
      {
        QString msg = QString("Service class %1 is not an instance of %2. Maybe you forgot the Q_INTERFACES macro in the service class.")
            .arg(service->metaObject()->className()).arg(cls);
        throw CFSInvalidArgumentException(msg);
      }
    }
  }

  CFSServiceRegistration res(plugin, service,
                             createServiceProperties(properties, classes));
  {
    QMutexLocker lock(&mutex);
    services.insert(res, classes);
    for (QStringListIterator i(classes); i.hasNext(); )
    {
      QString currClass = i.next();
      QList<CFSServiceRegistration>& s = classServices[currClass];
      QList<CFSServiceRegistration>::iterator ip =
          std::lower_bound(s.begin(), s.end(), res, ServiceRegistrationComparator());
      s.insert(ip, res);
    }
  }

  CFSServiceReference r = res.getReference();
  plugin->fwCtx->listeners.serviceChanged(
      plugin->fwCtx->listeners.getMatchingServiceSlots(r),
      CFSServiceEvent(CFSServiceEvent::REGISTERED, r));
  return res;
}

//----------------------------------------------------------------------------
void CFSServices::updateServiceRegistrationOrder(const CFSServiceRegistration& sr,
                                              const QStringList& classes)
{
  QMutexLocker lock(&mutex);
  for (QStringListIterator i(classes); i.hasNext(); )
  {
    QList<CFSServiceRegistration>& s = classServices[i.next()];
    s.removeAll(sr);
    s.insert(std::lower_bound(s.begin(), s.end(), sr, ServiceRegistrationComparator()), sr);
  }
}

//----------------------------------------------------------------------------
bool CFSServices::checkServiceClass(QObject* service, const QString& cls) const
{
  return service->inherits(cls.toLatin1());
}

//----------------------------------------------------------------------------
QList<CFSServiceRegistration> CFSServices::get(const QString& clazz) const
{
  QMutexLocker lock(&mutex);
  return classServices.value(clazz);
}

//----------------------------------------------------------------------------
CFSServiceReference CFSServices::get(CFSPluginPrivate* plugin, const QString& clazz) const
{
  QMutexLocker lock(&mutex);
  try {
    QList<CFSServiceReference> srs = get_unlocked(clazz, QString(), plugin);
    if (framework->debug.service_reference)
    {
      qDebug() << "get service ref" << clazz << "for plugin"
               << plugin->location << " = " << srs.size() << "refs";
    }
    if (!srs.isEmpty()) {
      return srs.front();
    }
  }
  catch (const CFSInvalidArgumentException& )
  { }

  return CFSServiceReference();
}

//----------------------------------------------------------------------------
QList<CFSServiceReference> CFSServices::get(const QString& clazz, const QString& filter,
                                            CFSPluginPrivate* plugin) const
{
  QMutexLocker lock(&mutex);
  return get_unlocked(clazz, filter, plugin);
}

//----------------------------------------------------------------------------
QList<CFSServiceReference> CFSServices::get_unlocked(const QString& clazz, const QString& filter,
                                                     CFSPluginPrivate* plugin) const
{
  Q_UNUSED(plugin)

  QListIterator<CFSServiceRegistration>* s = 0;
  QList<CFSServiceRegistration> v;
  CFSLDAPExpr ldap;
  if (clazz.isEmpty())
  {
    if (!filter.isEmpty())
    {
      ldap = CFSLDAPExpr(filter);
      QSet<QString> matched;
      if (ldap.getMatchedObjectClasses(matched))
      {
        v.clear();
        foreach (QString className, matched)
        {
          const QList<CFSServiceRegistration>& cl = classServices[className];
          v += cl;
        }
        if (!v.isEmpty())
        {
          s = new QListIterator<CFSServiceRegistration>(v);
        }
        else
        {
          return QList<CFSServiceReference>();
        }
      }
      else
      {
        s = new QListIterator<CFSServiceRegistration>(services.keys());
      }
    }
    else
    {
      s = new QListIterator<CFSServiceRegistration>(services.keys());
    }
  }
  else
  {
    QList<CFSServiceRegistration> v = classServices.value(clazz);
    if (!v.isEmpty())
    {
      s = new QListIterator<CFSServiceRegistration>(v);
    }
    else
    {
      return QList<CFSServiceReference>();
    }
    if (!filter.isEmpty())
    {
      ldap = CFSLDAPExpr(filter);
    }
  }

  QList<CFSServiceReference> res;
  while (s->hasNext())
  {
    CFSServiceRegistration sr = s->next();
    CFSServiceReference sri = sr.getReference();

    if (filter.isEmpty() || ldap.evaluate(sr.d_func()->properties, false))
    {
      res.push_back(sri);
    }
  }

  delete s;

  return res;
}

//----------------------------------------------------------------------------
void CFSServices::removeServiceRegistration(const CFSServiceRegistration& sr)
{
  QMutexLocker lock(&mutex);

  QStringList classes = sr.d_func()->properties.value(CFSPluginConstants::OBJECTCLASS).toStringList();
  services.remove(sr);
  for (QStringListIterator i(classes); i.hasNext(); )
  {
    QString currClass = i.next();
    QList<CFSServiceRegistration>& s = classServices[currClass];
    if (s.size() > 1)
    {
      s.removeAll(sr);
    }
    else
    {
      classServices.remove(currClass);
    }
  }
}

//----------------------------------------------------------------------------
QList<CFSServiceRegistration> CFSServices::getRegisteredByPlugin(CFSPluginPrivate* p) const
{
  QMutexLocker lock(&mutex);

  QList<CFSServiceRegistration> res;
  for (QHashIterator<CFSServiceRegistration, QStringList> i(services); i.hasNext(); )
  {
    CFSServiceRegistration sr = i.next().key();
    if (sr.d_func()->plugin == p)
    {
      res.push_back(sr);
    }
  }
  return res;
}

//----------------------------------------------------------------------------
QList<CFSServiceRegistration> CFSServices::getUsedByPlugin(QSharedPointer<CFSPlugin> p) const
{
  QMutexLocker lock(&mutex);

  QList<CFSServiceRegistration> res;
  for (QHashIterator<CFSServiceRegistration, QStringList> i(services); i.hasNext(); )
  {
    CFSServiceRegistration sr = i.next().key();
    if (sr.d_func()->isUsedByPlugin(p))
    {
      res.push_back(sr);
    }
  }
  return res;
}

